package com.github.llyb120.qiuyasql.core;

import com.github.llyb120.qiuyasql.driver.MysqlDriver;
import com.github.llyb120.qiuyasql.driver.SqlDriver;
import com.github.llyb120.qiuyasql.entity.*;
import lombok.Builder;
import lombok.Cleanup;
import lombok.SneakyThrows;
import lombok.var;

import javax.sql.DataSource;
import java.math.BigDecimal;
import java.sql.*;
import java.util.*;
import java.util.stream.Collectors;

import static com.github.llyb120.qiuyasql.core.ReflectUtil.analyze;
import static com.github.llyb120.qiuyasql.entity.FieldType.DECIMAL;
import static com.github.llyb120.qiuyasql.entity.FieldType.STRING;

/**
 * @Author: Administrator
 * @Date: 2020/10/5 15:26
 */
@Builder
public class QiuyaSql {

    private DataSource dataSource;
    private SqlDriver sqlDriver;// = new MysqlDriver();
    private boolean forceSync = false;


    public void insert(Object item) {
        insert(Arrays.asList(item));
    }

    @SneakyThrows
    public <T> void insert(List<T> items) {
        if (items == null) {
            return;
        }
        if (items.isEmpty()) {
            return;
        }
        TableEntity table = analyze(items.get(0).getClass());
        if (table == null) {
            return;
        }

        @Cleanup var connection = dataSource.getConnection();
        doInsert(items, table, connection, true);
    }

    @SneakyThrows
    private void doInsert(List<?> items, TableEntity table, Connection connection, boolean autoCreate) {
        StringBuilder sb = new StringBuilder();
        StringBuilder sb2 = new StringBuilder();
        sb.append("insert into ");
        sb.append("`");
        sb.append(table.getTable());
        sb.append("`");
        sb.append("(");
        for (TableField field : table.getFields()) {
            sb.append("`");
            sb.append(field.getName());
            sb.append("`");
            sb.append(",");
            sb2.append("?,");
        }
        sb.deleteCharAt(sb.length() - 1);
        sb2.deleteCharAt(sb2.length() - 1);
        sb2.append(")");
        sb.append(") values (");
        sb.append(sb2);

        @Cleanup var stmt = connection.prepareStatement(sb.toString());
        for (Object item : items) {
            int i = 1;
            for (TableField field : table.getFields()) {
                switch (field.getType()) {
                    case DECIMAL:
                        stmt.setBigDecimal(i, (BigDecimal) field.getValue(item));
                        break;

                    case STRING:
                        stmt.setString(i, (String) field.getValue(item));
                        break;
                }
                i++;
            }
            stmt.addBatch();
        }
        if (autoCreate) {
            try {
                stmt.executeBatch();
            } catch (SQLException e) {
                if(sqlDriver.hasWrongTable(e)){
                    try{
                        createTable(table);
                    } catch (Exception ee){
                        throw new RuntimeException(String.format("创建表 %s 失败", table.getTable()), e);
                    }
                    doInsert(items, table, connection, false);
                    return;
                }
                if (sqlDriver.hasWrongColumn(e)) {
                    var patch = calculatePatch(table);
                    try{
                        executeSql(sqlDriver.buildAlterTableColumnSql(table, patch));//.getLessFields(), patch.getChangedFields()));
                    } catch (Exception ee){
                        if(forceSync){
                            //如果强行同步，则先删除字段，后重新创建
                            //todo:
                            System.out.println("ff");
                        } else {
                            throw new RuntimeException(String.format("同步表 %s 失败，请手动同步", table.getTable()), e);
                        }
                    }
                    doInsert(items, table, connection, false);
                    return;
                }
                throw e;
            }
        } else {
            stmt.executeBatch();
        }
    }

    public TablePatch calculatePatch(TableEntity entity) throws SQLException {
        List<MysqlTable> fields = querySql("desc `" + entity.getTable() + '`', MysqlTable.class);
        //缺少的
        var leftMap = new HashMap<String, MysqlTable>();
        for (MysqlTable field : fields) {
            leftMap.put(field.getField(), field);
        }
        var rightMap = entity.getFieldMap();
        var lessFields = new ArrayList<TableField>();
        var changedFields = new ArrayList<TableField>();
        for (String key : rightMap.keySet()) {
            var leftField = leftMap.get(key);
            var rightField = rightMap.get(key);
            if (leftField == null) {
                lessFields.add(rightField);
            } else {
                //比较类型
                var type = sqlDriver.getTypeFromField(leftField.getType());
                if (type != rightField.getType()) {
                    changedFields.add(rightField);
                }
            }
        }
        return new TablePatch(lessFields, changedFields);
//        String sql = sqlDriver.buildAlterTableColumnSql(entity, lessFields, changedFields);
//        executeSql(sql);
    }

    @SneakyThrows
    public void createTable(TableEntity entity) {
        executeSql(sqlDriver.buildCreateTableSql(entity));
    }


    @SneakyThrows
    public void executeSql(String sql) {
        @Cleanup var conn = dataSource.getConnection();
        executeSql(sql, conn);
    }

    @SneakyThrows
    public void executeSql(String sql, Connection connection) {
        @Cleanup var stmt = connection.createStatement();
        stmt.executeUpdate(sql);
    }

    @SneakyThrows
    public List<Map<String, ?>> querySql(Connection connection, String sql) {
        @Cleanup PreparedStatement preparedStatement = connection.prepareStatement(sql);
        @Cleanup ResultSet resultSet = preparedStatement.executeQuery();
        ResultSetMetaData rsmd = resultSet.getMetaData();
        List<Map<String, ?>> result = new ArrayList<>();
        while (resultSet.next()) {
            Map<String, ? super Object> map = new HashMap<>();
            for (int i = 1; i < rsmd.getColumnCount() + 1; i++) {
                String columnName = rsmd.getColumnLabel(i);//.toLowerCase();
                switch (rsmd.getColumnType(i)) {
                    case Types.LONGVARCHAR:
                    case Types.VARCHAR:
                        map.put(columnName, resultSet.getString(i));
                        break;
                }
            }
            result.add(map);
        }
        return result;
    }

    @SneakyThrows
    public List<Map<String, ?>> querySql(String sql) {
        @Cleanup var conn = dataSource.getConnection();
        return querySql(conn, sql);
    }

    public <T> List<T> querySql(String sql, Class<T> resultClass){
        var list = querySql(sql);
        list = list.stream()
            .map(e -> {
                var map = new HashMap<String,Object>();
                e.forEach((k,v) -> {
                    map.put(toCamelCase(k), v);
                });
                return map;
            })
            .collect(Collectors.toList());
        List<T> ret = MapperUtils.mapAsList(resultClass, list);
        return ret;
    }

    private static String toCamelCase(CharSequence name) {
        if (null == name) {
            return null;
        } else {
            String name2 = name.toString().toLowerCase();
            if (name2.contains("_")) {
                StringBuilder sb = new StringBuilder(name2.length());
                boolean upperCase = false;

                for(int i = 0; i < name2.length(); ++i) {
                    char c = name2.charAt(i);
                    if (c == '_') {
                        upperCase = true;
                    } else if (upperCase) {
                        sb.append(Character.toUpperCase(c));
                        upperCase = false;
                    } else {
                        sb.append(Character.toLowerCase(c));
                    }
                }

                return sb.toString();
            } else {
                return name2;
            }
        }
    }
}
